package com.istock.union.user.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.RandomStringUtils;

/**
 * Description:MD5加密
 *
 */
public class MD5Utils {

	private static final String ALGORITHM_MD5 = "MD5";
	
	private static final Integer DEFAULT_SALT_BYTE = 8;
	
	/**
	 * 普通MD5
	 * 手动每个字节转换成16位长度，也可以使用如方法md5Hex中所示 new Hex().encode()来转码
	 * 
	 * @param key
	 * @return
	 */
	public static String genMD5(String key) {
		MessageDigest md5 = null;
		try {
			md5 = MessageDigest.getInstance(ALGORITHM_MD5);
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}
		
		byte[] digest = md5.digest(key.getBytes());
		StringBuilder sb = new StringBuilder();
		for (byte b : digest) {
			int var = b & 0xff;
			if (var < 16) {
				sb.append("0");
			}
			sb.append(Integer.toHexString(var));
		}
		return sb.toString();
	}
	
	/**
	 * 固定加盐MD5（MD5(key+salt)）
	 * 
	 * @param key
	 * @param salt
	 * @return
	 */
	public static String genMD5(String key, String salt) {
		return md5Hex(key + salt);
	}
	
	/**
	 * 固定加盐散列MD5（mixed(MD5(key+salt))）
	 * 
	 * @param key
	 * @param salt
	 * @return
	 */
	public static String genSaltMD5(String key, String salt) {
		return encode(key, salt);
	}

	/**
	 * 动态加盐MD5（MD5值与定长动态盐散列）
	 * 
	 * @param key
	 * @return
	 */
	public static String genSaltMD5(String key) {
		return genSaltMD5(key, DEFAULT_SALT_BYTE);
	}
	
	/**
	 * 动态加盐MD5（MD5值与自定义长度动态盐散列）
	 * 
	 * @param key
	 * @param saltLen
	 * @return
	 */
	public static String genSaltMD5(String key, int saltLen) {
		if (saltLen == 0) {
			saltLen = DEFAULT_SALT_BYTE;
		}
		return encode(key, genSalt(saltLen));
	}

	/**
	 * 自定义加盐散列
	 * 
	 * @param key
	 * @param salt
	 * @return
	 */
	private static String encode(String key, String salt) {
		key = md5Hex(key + salt);
		char[] encryChars = new char[salt.length() + key.length()];
		
		// 获得相对均匀分布的下标
		List<Integer> keyIdx = new ArrayList<Integer>(10);
		List<Integer> saltIdx = new ArrayList<Integer>(10);
		int regionLen = hashIndex(keyIdx, saltIdx, salt.length(), key.length());
		
		// 编码
		int keyLength = 0;
		int saltLength = 0;
		for (int i = 0; i < encryChars.length; i+= regionLen) {
			for (Integer idx : keyIdx) {
				encryChars[i + idx] = key.charAt(keyLength);
				keyLength++;
			}
			for (Integer idx : saltIdx) {
				encryChars[i + idx] = salt.charAt(saltLength);
				saltLength++;
			}
		}
		
		return new String(encryChars);
	}

	/**
	 * 计算散列下标，并返回散列区间长度
	 * 
	 * @param keyIdx
	 * @param saltIdx
	 * @param saltLen
	 * @param keyMD5Len
	 * @return
	 */
	private static int hashIndex(List<Integer> keyIdx, List<Integer> saltIdx, int saltLen, int keyMD5Len) {
		int num = maxCommonDivisor(saltLen, keyMD5Len);
		int saltScale = saltLen / num;
		int keyScale = keyMD5Len / num;
		int regionLen = saltScale + keyScale;
		// 先放长度更大的
		if (keyScale > saltScale && keyScale % saltScale >= 0) {
			caculateIndex(keyIdx, saltIdx, keyScale, saltScale);
		} else if (keyScale < saltScale && saltScale % keyScale >= 0) {
			caculateIndex(saltIdx, keyIdx, saltScale, keyScale);
		} else if (keyScale == saltScale) {
			// 等长时,后放盐
			caculateIndex(keyIdx, saltIdx, keyScale, saltScale);
		}
		return regionLen;
	}
	
	/**
	 * 计算散列下标，简单散列，间距为1
	 * 
	 * @param first
	 * @param second
	 * @param fistScale
	 * @param secondScale
	 */
	private static void caculateIndex(List<Integer> first, List<Integer> second, int firstScale, int secondScale) {
		int scope = firstScale + secondScale;
		int index = 0;
		while (index < scope) {
			first.add(index);
			index += 2;
			if (first.size() >= firstScale) {
				break;
			}
		}
		index = 1;
		while (index < scope) {
			second.add(index);
			index += 2;
			if (second.size() >= secondScale) {
				break;
			}
		}
		
		// 补充未满下标
		if (first.size() + second.size() == scope) {
			return;
		}
		
		List<Integer> regionIndex = new ArrayList<Integer>(scope);
		for (int i = 0; i < scope - 1; i++) {
			regionIndex.add(i);
		}
		regionIndex.removeAll(first);
		regionIndex.removeAll(second);
		if (firstScale > secondScale) {
			first.addAll(regionIndex);
		} else {
			second.addAll(regionIndex);
		}
	}
	
	/**
	 * 求最大公约数
	 * 
	 * @param num
	 * @return
	 */
	public static int maxCommonDivisor(int... nums) {
		Arrays.sort(nums);
		for (int i = 0; i < nums.length - 1; i++) {
			nums[i + 1] = maxCommonDivisor(nums[i], nums[i + 1]);
		}
		return nums[nums.length - 1];
	}
	
	/**
	 * 求两个数的最大公约数
	 * 
	 * @param num1
	 * @param num2
	 * @return
	 */
	private static int maxCommonDivisor(int num1, int num2) {
		int temp = 0;
		if (num1 < num2) {
			temp = num1;
			num1 = num2;
			num2 = temp;
		}
		while (num2 != 0) {
			if (num1 == num2) {
				return num1;
			} else {
				int k = num1 % num2;
				num1 = num2;
				num2 = k;
			}
		}
		return num1;
	}

	/**
	 * MD5加密
	 * 通过Hex加密
	 * 
	 * @param key
	 * @return
	 */
	private static String md5Hex(String key) {
		try {
			MessageDigest md5 = MessageDigest.getInstance(ALGORITHM_MD5);
			byte[] digest = md5.digest(key.getBytes());
			return new String(new Hex().encode(digest));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return "";
		}
	}
	
	/**
	 * HEX生成定长随机盐
	 * 
	 * @return
	 */
	public static String genSalt() {
		return Hex.encodeHexString(generateSeed(DEFAULT_SALT_BYTE));
	}
	
	/**
	 * HEX按长度生成随机盐
	 * 
	 * @param length
	 * @return
	 */
	public static String genSalt(int length) {
		if (length == 0) {
			length = DEFAULT_SALT_BYTE;
		}
		return Hex.encodeHexString(generateSeed(length));
	}
	
	/**
	 * HEX生成定长随机盐
	 * 
	 * @return
	 */
	public static String genSecureSalt() {
		return Hex.encodeHexString(generateSecureSeed(DEFAULT_SALT_BYTE));
	}
	
	/**
	 * HEX按长度生成随机盐
	 * 
	 * @param length
	 * @return
	 */
	public static String genSecureSalt(int length) {
		if (length == 0) {
			length = DEFAULT_SALT_BYTE;
		}
		return Hex.encodeHexString(generateSecureSeed(length));
	}
	
	/**
	 * 使用32-126位Ascii码获取随机字符串
	 * 
	 * @param length
	 * @return
	 */
	private static byte[] generateSeed(int length) {
		return RandomStringUtils.randomAscii(length).getBytes();
	}
	
	/**
	 * 获取安全随机种子，Linux环境下性能极差
	 * 
	 * @param length
	 * @return
	 */
	private static byte[] generateSecureSeed(int length) {
		try {
			SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
			return secureRandom.generateSeed(length);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 验证
	 * 与普通MD5(genMD5)对应，
	 * 
	 * @param key 需要校验的原值
	 * @param md5 需要验证的密值
	 * @return
	 */
	public static boolean verify(String key, String md5) {
		return genMD5(key).equals(md5);
	}
	
	/**
	 * 验证
	 * 与普通MD5(genMD5)对应
	 * 
	 * @param key 需要校验的原值
	 * @param salt 用于加密的盐
	 * @param md5 需要验证的密值
	 * @return
	 */
	public static boolean verify(String key, String salt, String md5) {
		return genMD5(key, salt).equals(md5);
	}
	
	/**
	 * 验证
	 * 与固定加盐MD5(genSaltMD5)对应，
	 * 
	 * @param key 需要校验的原值
	 * @param salt 用于校验的盐
	 * @param md5 需要验证的密值
	 * @return
	 */
	public static boolean verifySaltMD5(String key, String salt, String md5) {
		return genSaltMD5(key, salt).equals(md5);
	}
	
	
	/**
	 * 验证
	 * 与动态加盐MD5(genSaltMixMD5)对应
	 * 
	 * @param key 要验证的字符串
	 * @param md5 加密的字符串
	 * @return
	 */
	public static boolean verifySaltMD5(String key, String md5) {
		return verifySaltMD5(key, DEFAULT_SALT_BYTE, md5);
	}
	
	/**
	 * 验证
	 * 与动态加盐MD5(genSaltMixMD5)对应，
	 * 分别获取密码MD值和盐值，再使用盐对新的key加密后与原md5对比
	 * 
	 * @param key 需要校验的原值
	 * @param saltLen 用于校验的盐
	 * @param md5  需要验证的密值
	 * @return
	 */
	public static boolean verifySaltMD5(String key, int saltLen, String md5) {
		if (saltLen == 0) {
			saltLen = DEFAULT_SALT_BYTE;
		}
		saltLen = saltLen << 1;
		
		String[] decode = decode(saltLen, md5);
		
		return md5Hex(key + decode[0]).equals(decode[1]);
	}
	
	/**
	 * 自定义解码、与encode对应
	 * 
	 * @param saltLen
	 * @param md5
	 * @return
	 */
	private static String[] decode(int saltLen, String md5) {
		// 解码密码
		char[] pwdChars = new char[md5.length() - saltLen];
		// 解码盐
		char[] saltChars = new char[saltLen];
		
		// 获得相对均匀分布的下标
		List<Integer> keyIdx = new ArrayList<Integer>(10);
		List<Integer> saltIdx = new ArrayList<Integer>(10);
		int regionLen = hashIndex(keyIdx, saltIdx, saltLen, md5.length() - saltLen);
		
		// 解码
		int keyLength = 0;
		int saltLength = 0;
		for (int i = 0; i < md5.length(); i+= regionLen) {
			for (Integer idx : keyIdx) {
				pwdChars[keyLength] = md5.charAt(i + idx);
				keyLength++;
			}
			for (Integer idx : saltIdx) {
				saltChars[saltLength] = md5.charAt(i + idx);
				saltLength++;
			}
		}
		return new String[]{new String(saltChars), new String(pwdChars)};
	}
	
	public static void main(String[] args) {
		/*for (int i = 0; i < 50; i++) {
			String key = "admin" + "admin123";
			System.out.println(key);
			String passwd = genMD5(key);
			System.out.println(passwd);
			System.out.println(verify(key, passwd));
			System.out.println("------------------------这是一条分割线-------------------------");
			String salt = genSalt();
			System.out.println(salt);
			key = key + salt;
			passwd = genMD5(key, salt);
			System.out.println(passwd);
			System.out.println(verify(key, salt, passwd));
			System.out.println("------------------------这是一条分割线-------------------------");
			passwd = genSaltMD5(key);
			System.out.println(passwd);
			System.out.println(verifySaltMD5(key, passwd));
			System.out.println("------------------------这是一条分割线-------------------------");
			passwd = genSaltMD5(key, salt);
			System.out.println(passwd);
			System.out.println(verifySaltMD5(key, salt, passwd));
			System.out.println("------------------------这是一条分割线-------------------------");
			int saltByteLen = 6;
			salt = genSalt(saltByteLen);
			System.out.println(salt);
			passwd = genSaltMD5(key, saltByteLen);
			System.out.println(passwd);
			System.out.println(verifySaltMD5(key, saltByteLen, passwd));
		}*/
		
		String key = "admin123";
		String salt = genSalt();
		String passwd = genSaltMD5(key, salt);
		System.out.println(verifySaltMD5(key, passwd));
		System.out.println("passwd====="+passwd+",salt========"+salt);
	}
	
}
